package com.ygqh.baby.service.wechat.api;

import com.alibaba.fastjson.TypeReference;
import com.foxinmy.weixin4j.api.CashApi;
import com.foxinmy.weixin4j.api.Pay3Api;
import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.exception.WeixinPayException;
import com.foxinmy.weixin4j.http.weixin.WeixinRequestExecutor;
import com.foxinmy.weixin4j.http.weixin.WeixinResponse;
import com.foxinmy.weixin4j.http.weixin.WeixinSSLRequestExecutor;
import com.foxinmy.weixin4j.model.Consts;
import com.foxinmy.weixin4j.model.WeixinPayAccount;
import com.foxinmy.weixin4j.payment.PayURLConsts;
import com.foxinmy.weixin4j.payment.mch.*;
import com.foxinmy.weixin4j.type.CurrencyType;
import com.foxinmy.weixin4j.type.IdQuery;
import com.foxinmy.weixin4j.type.TradeType;
import com.foxinmy.weixin4j.util.DateUtil;
import com.foxinmy.weixin4j.util.DigestUtil;
import com.foxinmy.weixin4j.util.RandomUtil;
import com.foxinmy.weixin4j.util.StringUtil;
import com.foxinmy.weixin4j.xml.XmlStream;
import com.ygqh.baby.ao.DataStatus;
import com.ygqh.baby.ao.PayType;
import com.ygqh.baby.po.YgOrderPayLog;
import com.ygqh.baby.service.YgOrderPayLogService;
import com.ygqh.baby.service.YgOrderPaymentService;
import com.ygqh.baby.service.wechat.model.WxPayAccount;
import com.ygqh.baby.service.wechat.notify.YgMchPayPackage;

import javax.xml.bind.SchemaOutputResolver;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class YgPay3Api {
    private final WeixinPayAccount ygWeixinAccount;
    private final WeixinRequestExecutor weixinExecutor;

    private YgOrderPayLogService ygOrderPayLogService;

    public void setYgOrderPayLogService(YgOrderPayLogService ygOrderPayLogService) {
        this.ygOrderPayLogService = ygOrderPayLogService;
    }

    public YgPay3Api(WeixinPayAccount ygWeixinAccount) {
        this.ygWeixinAccount = ygWeixinAccount;
        this.weixinExecutor = new WeixinRequestExecutor();
    }

    /**
     * 统一下单接口</br>
     * 除被扫支付场景以外，商户系统先调用该接口在微信支付服务后台生成预支付交易单，返回正确的预支付交易回话标识后再按扫码、JSAPI
     * 、APP等不同场景生成交易串调起支付。
     *
     * @param payPackage 包含订单信息的对象
     * @return 预支付对象
     * @see com.foxinmy.weixin4j.payment.mch.MchPayPackage
     * @see com.foxinmy.weixin4j.payment.mch.PrePay
     * @see <a
     * href="http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1">统一下单接口</a>
     */
    public PrePay createPrePay(YgMchPayPackage payPackage) throws WeixinPayException {
        if (StringUtil.isBlank(payPackage.getSign())) {
            payPackage.setSign(DigestUtil.paysignMd5(payPackage, ygWeixinAccount.getPaySignKey()));
        }
        String payJsRequestXml = XmlStream.toXML(payPackage);
        WeixinResponse response = null;
        try {
            System.out.println(payJsRequestXml);
            response = weixinExecutor.post(PayURLConsts.MCH_UNIFIEDORDER_URL, payJsRequestXml);
            PrePay prePay = response.getAsObject(new TypeReference<PrePay>() {
            });
            if (!prePay.getReturnCode().equalsIgnoreCase(Consts.SUCCESS)) {
                throw new WeixinPayException(prePay.getReturnMsg(), prePay.getReturnCode());
            }
            if (!prePay.getResultCode().equalsIgnoreCase(Consts.SUCCESS)) {
                throw new WeixinPayException(prePay.getResultCode(), prePay.getErrCodeDes());
            }
            return prePay;
        } catch (WeixinException e) {
            e.printStackTrace();
            throw new WeixinPayException(e.getErrorCode(), e.getErrorMsg());
        } finally {
            try {
                if (ygOrderPayLogService != null) {
                    List<YgOrderPayLog> logs = new ArrayList<>();
                    YgOrderPayLog request = new YgOrderPayLog(payPackage.getOutTradeNo(),payPackage.getOutTradeNo(), PayType.WeChat.name(), "Resquest",
                            payJsRequestXml, DataStatus.Valid.name());
                    YgOrderPayLog responseMsg = new YgOrderPayLog(payPackage.getOutTradeNo(),payPackage.getOutTradeNo(), PayType.WeChat.name(), "Response",
                            response == null ? "支付异常" : response.getAsString(), DataStatus.Valid.name());
                    logs.add(request);
                    logs.add(responseMsg);
                    //添加支付请求记录
                    ygOrderPayLogService.addPayLog(logs);
                }
            }catch (Exception ignored){

            }

        }
    }

    /**
     * 创建JSAPI支付请求对象
     *
     * @param openId     用户ID
     * @param body       订单描述
     * @param outTradeNo 订单号
     * @param totalFee   订单总额 按实际金额传入即可(元) 构造函数会转换为分
     * @param notifyUrl  支付通知地址
     * @param createIp   ip地址
     * @return JSAPI支付对象
     * @throws WeixinPayException
     * @see com.foxinmy.weixin4j.payment.mch.JSAPIPayRequest
     */
    public MchPayRequest createJSPayRequest(String openId, String body, String outTradeNo, int totalFee, String notifyUrl, String createIp)
            throws WeixinPayException {
        MchPayPackage payPackage = new MchPayPackage(ygWeixinAccount, openId, body, outTradeNo, totalFee, notifyUrl, createIp, TradeType.JSAPI);
        Pay3Api pay3Api = new Pay3Api(ygWeixinAccount);
        return pay3Api.createPayRequest(payPackage);
    }

    /**
     * 申请退款(请求需要双向证书)
     * <p>
     * 当交易发生之后一段时间内，由于买家或者卖家的原因需要退款时，卖家可以通过退款接口将支付款退还给买家，微信支付将在收到退款请求并且验证成功之后，
     * 按照退款规则将支付款按原路退到买家帐号上。
     * </p>
     * <p style="color:red">
     * 1.交易时间超过半年的订单无法提交退款；
     * 2.微信支付退款支持单笔交易分多次退款，多次退款需要提交原支付订单的商户订单号和设置不同的退款单号。一笔退款失败后重新提交
     * ，要采用原来的退款单号。总退款金额不能超过用户实际支付金额。
     * </p>
     *
     * @param ca            后缀为*.p12的证书文件
     * @param idQuery       商户系统内部的订单号, transaction_id 、 out_trade_no 二选一,如果同时存在优先级:
     *                      transaction_id> out_trade_no
     * @param outRefundNo   商户系统内部的退款单号,商 户系统内部唯一,同一退款单号多次请求只退一笔
     * @param totalFee      订单总金额,单位为元
     * @param refundFee     退款总金额,单位为元,可以做部分退款
     * @param refundFeeType 货币类型，符合ISO 4217标准的三位字母代码，默认人民币：CNY
     * @param opUserId      操作员帐号, 默认为商户号
     * @return 退款申请结果
     * @throws WeixinException
     * @see com.foxinmy.weixin4j.payment.mch.RefundResult
     * @see <a
     * href="http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4">申请退款API</a>
     * @since V3
     */
    public String refundApply(InputStream ca, IdQuery idQuery, String outRefundNo, double totalFee, double refundFee, CurrencyType refundFeeType,
                              String opUserId) throws WeixinException {
        WeixinResponse response = null;
        try {
            Map<String, String> map = baseMap(idQuery);
            map.put("out_refund_no", outRefundNo);
            map.put("total_fee", DateUtil.formaFee2Fen(totalFee));
            map.put("refund_fee", DateUtil.formaFee2Fen(refundFee));
            if (StringUtil.isBlank(opUserId)) {
                opUserId = ygWeixinAccount.getMchId();
            }
            map.put("op_user_id", opUserId);
            if (refundFeeType == null) {
                refundFeeType = CurrencyType.CNY;
            }
            map.put("refund_fee_type", refundFeeType.name());
            String sign = DigestUtil.paysignMd5(map, ygWeixinAccount.getPaySignKey());
            map.put("sign", sign);
            String param = XmlStream.map2xml(map);
            String certificateKey = ygWeixinAccount.getCertificateKey();
            System.out.println("certkey ===============================" + certificateKey);
            WeixinRequestExecutor weixinExecutor = new WeixinSSLRequestExecutor(certificateKey, ca);
            response = weixinExecutor.post(PayURLConsts.MCH_REFUNDAPPLY_URL, param);
        } finally {
            if (ca != null) {
                try {
                    ca.close();
                } catch (IOException e) {
                    ;
                }
            }
        }

        return response.getAsString();
    }

    /**
     * 退款查询
     *
     * <p>
     * 提交退款申请后，通过调用该接口查询退款状态。退款有一定延时，用零钱支付的退款20分钟内到账，银行卡支付的退款3个工作日后重新查询退款状态。
     * </p>
     *
     * @param idQuery 单号 refund_id、out_refund_no、 out_trade_no 、 transaction_id
     *                四个参数必填一个,优先级为:
     *                refund_id>out_refund_no>transaction_id>out_trade_no
     * @return 退款记录
     * @throws WeixinException
     * @see com.foxinmy.weixin4j.payment.mch.RefundRecord
     * @see com.foxinmy.weixin4j.payment.mch.RefundDetail
     * @see <a
     * href="http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_5">退款查询API</a>
     * @since V3
     */
    public String refundQuery(IdQuery idQuery) throws WeixinException {
        Map<String, String> map = baseMap(idQuery);
        String sign = DigestUtil.paysignMd5(map, ygWeixinAccount.getPaySignKey());
        map.put("sign", sign);
        String param = XmlStream.map2xml(map);
        WeixinResponse response = weixinExecutor.post(PayURLConsts.MCH_REFUNDQUERY_URL, param);
        return response.getAsString();
    }

    /**
     * 微信发红包接口
     *
     * @param redpacket
     * @param ca
     * @return RedpacketSendResult 返回类型
     * @throws WeixinException 入参
     * @throws
     * @Title: sendredpack
     * @author （guohao）
     * @date 2017年9月5日 上午11:29:14
     * @version V1.0
     */
    public RedpacketSendResult sendredpack(Redpacket redpacket, InputStream ca) throws WeixinException {
        CashApi api = new CashApi(ygWeixinAccount);
        RedpacketSendResult sendRedpack = api.sendRedpack(ca, redpacket);
        return sendRedpack;
    }

    /**
     * 查询红包
     *
     * @param outTradeNo
     * @param ca
     * @return RedpacketRecord 返回类型
     * @throws WeixinException 入参
     * @throws
     * @Title: sendredpack
     * @author （guohao）
     * @date 2017年9月13日 上午10:54:55
     * @version V1.0
     */
    public RedpacketRecord queryRedpack(String outTradeNo, InputStream ca) throws WeixinException {
        CashApi api = new CashApi(ygWeixinAccount);
        RedpacketRecord result = api.queryRedpack(ca, outTradeNo);
        return result;
    }

    /**
     * V3接口请求基本数据
     *
     * @return
     */
    private Map<String, String> baseMap(IdQuery idQuery) {
        Map<String, String> map = new HashMap<String, String>();
        map.put("appid", ygWeixinAccount.getId());
        map.put("mch_id", ygWeixinAccount.getMchId());
        map.put("nonce_str", RandomUtil.generateString(16));
        if (StringUtil.isNotBlank(ygWeixinAccount.getDeviceInfo())) {
            map.put("device_info", ygWeixinAccount.getDeviceInfo());
        }
        if (idQuery != null) {
            map.put(idQuery.getType().getName(), idQuery.getId());
        }
        if (ygWeixinAccount instanceof WxPayAccount) {
            WxPayAccount wxAccount = (WxPayAccount) ygWeixinAccount;
            if (StringUtil.isNotBlank(wxAccount.getSubId())) {
                map.put("sub_appid", wxAccount.getSubId());
            }
            if (StringUtil.isNotBlank(wxAccount.getSubMchId())) {
                map.put("sub_mch_id", wxAccount.getSubMchId());
            }
        }
        return map;
    }

    /**
     * 设置商户信息
     *
     * @param merchant
     */
    protected <T extends MerchantResult> void declareMerchant(T merchant) {
        merchant.setAppId(ygWeixinAccount.getId());
        merchant.setMchId(ygWeixinAccount.getMchId());
        merchant.setDeviceInfo(ygWeixinAccount.getDeviceInfo());
        merchant.setSubAppId(ygWeixinAccount.getSubMchId());
        merchant.setSubMchId(ygWeixinAccount.getSubMchId());
        merchant.setNonceStr(RandomUtil.generateString(16));
    }

    public RefundResult refundByProvider(InputStream ca, IdQuery idQuery, String outRefundNo, double totalFee, double refundFee) throws WeixinException {
        WeixinResponse response = null;
        try {
            Map<String, String> map = baseMap(idQuery);
            map.put("out_refund_no", outRefundNo);
            map.put("total_fee", DateUtil.formaFee2Fen(totalFee));
            map.put("refund_fee", DateUtil.formaFee2Fen(refundFee));
            map.put("op_user_id", ygWeixinAccount.getMchId());
            map.put("refund_fee_type", CurrencyType.CNY.name());
            String sign = DigestUtil.paysignMd5(map, ygWeixinAccount.getPaySignKey());
            map.put("sign", sign);
            String param = XmlStream.map2xml(map);
            WeixinRequestExecutor weixinExecutor = new WeixinSSLRequestExecutor(ygWeixinAccount.getCertificateKey(), ca);
            response = weixinExecutor.post(PayURLConsts.MCH_REFUNDAPPLY_URL, param);
        } finally {
            if (ca != null) {
                try {
                    ca.close();
                } catch (IOException e) {
                    ;
                }
            }
        }
        return response.getAsObject(new TypeReference<RefundResult>() {
        });
    }
}
